Kotlin Extension — Method 对大多数Java开发者来说(尤其是享受着Android工程顶尖架构的Android Developer),装饰者模式 是我们经常接触的一种设计模式。装饰者模式带给开发者的灵活性和简洁性如何在Kotlin中也能用到呢?答案就是 —— Kotlin Extension。 但需要记住,可以用Extension来实现设计中的装饰者模式,但其绝对不仅仅只是个Kotlin版的装饰者模式!
正文 当我们使用Extension特性的时候,需要指定一个Receiver , 例如
其Receiver 就是C。
1. Extensions resolving 假设我们写一个简单的类Parent (in Parent.kt):
1 2 3 class Parent { val value : Int = 1 }
为了方便比较,再一些简单的类Other,同时在Other中写一个Parent的Extension markValue() (in Other.kt):
1 2 3 4 5 6 7 8 9 10 11 class Other { val const = 2 fun Parent.markValueOne() : Int { return this.value + 1 } } fun Parent.markValueTwo() : Int { return this.value + 2 }
在经过kotlinc编译之后,通过javap查看相关的class文件,markValueOne 和 markValueTwo 都被编译成了final 的方法。Other.class
from "Other.kt" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public final class com.maxtropy.viewtest.Other { public final int getConst(); Code: 0: aload_0 1: getfield #11 // Field const:I 4: ireturn public final int markValueOne(com.maxtropy.viewtest.Parent); Code: 0: aload_1 1: ldc #18 // String $receiver 3: invokestatic #24 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_1 7: invokevirtual #29 // Method com/maxtropy/viewtest/Parent.getValue:()I 10: iconst_1 11: iadd 12: ireturn public com.maxtropy.viewtest.Other(); Code: 0: aload_0 1: invokespecial #34 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_2 6: putfield #11 // Field const:I 9: return }
OtherKt.class
from "Other.kt" 1 2 3 4 5 6 7 8 9 10 11 12 public final class com.maxtropy.viewtest.OtherKt { public static final int markValueTwo(com.maxtropy.viewtest.Parent); Code: 0: aload_0 1: ldc #9 // String $receiver 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: aload_0 7: invokevirtual #21 // Method com/maxtropy/viewtest/Parent.getValue:()I 10: iconst_2 11: iadd 12: ireturn }
NOTE: 需要注意的是虽然markValueOne 的写法能够通过编译,但如果写的.kt,是拿不到markValueOne方法引用的。(令人奇怪的是写.java却能够拿到markValueOne方法的引用 ???) 很明显的可以看出来,无论是哪种写法,都会将Parent类型实例作为函数的第一个入参传进方法当中。而在方法中用到的this指针,指的便是这一个Parent实例,而不是传统Java编程习惯中this永远指向的是当前被调用实例方法对应的实例 。
2. 实例方法与Extension方法同名 倘若在Receiver类型中和Extension中声明了同名的方法呢?,例如:
在Parent中声明了一个方法markValueTwo()
1 2 3 4 5 6 7 class Parent { val value : Int = 1 fun markValueTwo(): Int { return this.value + 99 } }
然后在另一个类Other的.kt文件中使用Extension并让Extension的方法同Parent中的方法名一样
1 2 3 4 5 6 7 8 9 class Other { fun getParentMarkValue(): Int { return Parent().markValueTwo() } } fun Parent.markValueTwo(): Int { return this.value + 2 }
经过kotlinc的编译(在编译过程中其实就会有warning ), 会发现Other.class文件的中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Compiled from "Other.kt" public final class com.maxtropy.viewtest.Other { public final int getParentMarkValue(); Code: 0: new #8 // class com/maxtropy/viewtest/Parent 3: dup 4: invokespecial #12 // Method com/maxtropy/viewtest/Parent."<init>":()V 7: invokevirtual #15 // Method com/maxtropy/viewtest/Parent.markValueTwo:()I 10: ireturn public com.maxtropy.viewtest.Other(); Code: 0: aload_0 1: invokespecial #18 // Method java/lang/Object."<init>":()V 4: return }
可以看到,在字节码执行Parent实例的markValueTwo()方法时调用的是虚方法。也就是说,如果一个类型存在同名的实例方法和Extension方法,在使用时,真实调用的将是实例方法而不是Extension方法。